/*
Copyright 2010  Murali Devi (profdevi@gmail.com)

This file is part of ComineGL Project.

ComineGL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

ComineGL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with ComineGL.  If not, see <http://www.gnu.org/licenses/>.

This code uses the irrlicht and irrklang libraries.  Please read
the libraries copyrights at 

	irrlicht library:  http://irrlicht.sourceforge.net
	irrklang library:  http://www.ambiera.com/irrklang

*/



//v0.14 copyright Comine.com 20101212U01
#include <windows.h>
#include "MStdLib.h"
#include "Global.h"
#include "ComineGLMesh.h"



//***********************************************
//** ModuleElements
//***********************************************
static bool GMeshLoad(int meshid,IAnimatedMesh *panimmesh)
	{
	// Unload an existing Mesh
	if(GlobalMeshList.IsKey(meshid)==true)
		{
		GlobalMeshList.Remove(meshid);
		}

	TRefCount<MCGLMeshInfo> *newmesh;
	newmesh=new TRefCount<MCGLMeshInfo>;
	if(newmesh==NULL)
		{
		MStdPrintf("**Unable to alloc new mesh memory\n");
		return false;
		}

	if(newmesh->Create(panimmesh)==false)
		{
		MStdPrintf("**Unable to init mesh\n");
		newmesh->Drop();
		return false;
		}

	if(GlobalMeshList.Add(meshid,newmesh)==false)
		{
		MStdPrintf("**Unable to add mesh to table\n");
		newmesh->Drop();
		return false;
		}
	
	// Drop Reference to mesh
	newmesh->Drop();
	return true;	
	}

//***********************************************
//** Exported Functions
//***********************************************
MDLLFUN bool CGLMeshLoadFile(int meshid,const char *filename)
	{
	if(GlobalInitialized==false)
		{
		MStdPrintf("**Engine is not initialized\n");
		return false;
		}

	// Load up mesh
	IAnimatedMesh *panimmesh=GlobalRefSceneMgr->getMesh(filename);
	if(panimmesh==NULL)
		{
		MStdPrintf("**Unable to load mesh from file %s\n",filename);
		return false;
		}

	if(GMeshLoad(meshid,panimmesh)==false)
		{
		return false;
		}

	MStdPrintf("Loaded Mesh %d from file %s\n",meshid,filename);
	return true;
	}


/////////////////////////////////////////////
MDLLFUN bool CGLMeshLoadEmpty(int meshid)
	{
	if(GlobalInitialized==false)
		{
		MStdPrintf("**Engine is not initialized\n");
		return false;
		}

	SMesh *mesh=new SMesh;
	if(mesh==NULL)
		{
		return false;
		}

	// Create an empty Buffer
	SMeshBuffer *buffer=new SMeshBuffer;
	if(buffer==NULL)
		{	
		mesh->drop();
		return false;		
		}

	SAnimatedMesh *animmesh=new SAnimatedMesh();
	if(animmesh==NULL)
		{
		buffer->drop();
		mesh->drop();
		return false;
		}

	// Add Buffer to mesh
	mesh->addMeshBuffer(buffer);
	buffer->drop();

	// Add Mesh to Animated Mesh
	animmesh->addMesh(mesh);
	mesh->drop();

	//=We Now have a an animated mesh
	if(GMeshLoad(meshid,animmesh)==false)
		{
		animmesh->drop();
		return false;
		}

	animmesh->drop();
	return true;
	}


//////////////////////////////////////////////////
MDLLFUN bool CGLMeshLoadCylinder(int meshid,float radius, float height,int pies
		,int alpha,int red,int green,int blue)
	{
	if(GlobalInitialized==false)
		{
		MStdPrintf("**Engine is not initialized\n");
		return false;
		}

	// CGLMes

	// Allocate a block
	SMesh *mesh=new SMesh;
	if(mesh==NULL)
		{
		return NULL;
		}

	// Allocate Buffer
	SMeshBuffer *buffer=new SMeshBuffer;
	if(buffer==NULL)
		{	
		mesh->drop();
		return NULL;		
		}
	
	//**Build up the Cylinder Sides
	const int layers=1;
	int h;
	int k;
	float deltaheight=height/layers;
	float texheight=1.0f/layers;
	float texwidth=1.0f/pies;
	for(h=0;h<layers+1;++h)
		{
		for(k=0;k<pies;++k)
			{
			float angle=float(k*2*3.14159273f/pies);
			float cosangle=(float)cos(angle);
			float sinangle=(float)sin(angle);

			buffer->Vertices.push_back(
					S3DVertex(radius*cosangle
						,deltaheight*h-height/2
						,radius*sinangle
						,cosangle,0,sinangle
						,SColor(alpha,red,green,blue)
						,texwidth*k
						,texheight*(layers-h) ) );
			}
		}
	

	// Now Add the indices
	int points=pies*(layers+1);
	
	for(h=0;h<layers;++h)
		{
		for(k=0;k<pies;++k)
			{
			int index=h*pies+k;
			buffer->Indices.push_back(h*pies+(index+1)%pies);
			buffer->Indices.push_back(index);
			buffer->Indices.push_back(index+pies);

			buffer->Indices.push_back(h*pies+(index+1)%pies);
			buffer->Indices.push_back(index+pies);
			buffer->Indices.push_back((h+1)*pies+(index+1)%pies);
			}
		}

	//**Build up the Top of Cylinder
	int bottomindex=(layers+1)*pies;
	buffer->Vertices.push_back(S3DVertex(0,-height/2,0,0,-1,0,SColor(alpha,red,green,blue),0.5f,0.5f ) );
	buffer->Vertices.push_back(S3DVertex(0,+height/2,0,0,+1,0,SColor(alpha,red,green,blue),0.5f,0.5f ) );
	
	for(k=0;k<pies;++k)
		{
		buffer->Indices.push_back(bottomindex);
		buffer->Indices.push_back(k);
		buffer->Indices.push_back((k+1)%pies);

		buffer->Indices.push_back(layers*pies+k);
		buffer->Indices.push_back(bottomindex+1);
		buffer->Indices.push_back(layers*pies+(k+1)%pies);
		}
	
	mesh->addMeshBuffer(buffer);
	buffer->drop();

	// Add Bounding box
	mesh->setBoundingBox(aabbox3d<float>(-radius,-height/2,-radius,+radius,+height/2,+radius));

	SAnimatedMesh *animmesh=new SAnimatedMesh();
	if(animmesh==NULL)
		{
		mesh->drop();
		return NULL;
		}

	// Add the mesh to animated mesh
	animmesh->addMesh(mesh);
	mesh->drop();

	//=We Now have a an animated mesh
	if(GMeshLoad(meshid,animmesh)==false)
		{
		animmesh->drop();
		MStdPrintf("**Unable to load mesh %d\n",meshid);
		return false;
		}

	return true;
	}


//////////////////////////////////////////////////
MDLLFUN bool CGLMeshLoadBox(int meshid,float xwidth,float ywidth,float zwidth,
			int alpha,int red,int green,int blue)
	{
	if(GlobalInitialized==false)
		{
		MStdPrintf("**Engine is not initialized\n");
		return false;
		}

	SMesh *mesh=new SMesh;
	if(mesh==NULL)
		{
		return NULL;
		}

	// Allocate Buffer
	SMeshBuffer *buffer=new SMeshBuffer;
	if(buffer==NULL)
		{	
		mesh->drop();
		return NULL;		
		}

	// Front Face (0-3)
	buffer->Vertices.push_back(S3DVertex(-xwidth/2,-ywidth/2,-zwidth/2,0,0,-1,SColor(alpha,red,green,blue),0,1) );
	buffer->Vertices.push_back(S3DVertex(+xwidth/2,-ywidth/2,-zwidth/2,0,0,-1,SColor(alpha,red,green,blue),1,1) );
	buffer->Vertices.push_back(S3DVertex(+xwidth/2,+ywidth/2,-zwidth/2,0,0,-1,SColor(alpha,red,green,blue),1,0) );
	buffer->Vertices.push_back(S3DVertex(-xwidth/2,+ywidth/2,-zwidth/2,0,0,-1,SColor(alpha,red,green,blue),0,0) );

	buffer->Indices.push_back(0);
	buffer->Indices.push_back(2);
	buffer->Indices.push_back(1);

	buffer->Indices.push_back(0);
	buffer->Indices.push_back(3);
	buffer->Indices.push_back(2);

	// Top Face (4-7)
	buffer->Vertices.push_back(S3DVertex(-xwidth/2,-ywidth/2,+zwidth/2,0,0,+1,SColor(alpha,red,green,blue),0,1) );
	buffer->Vertices.push_back(S3DVertex(+xwidth/2,-ywidth/2,+zwidth/2,0,0,+1,SColor(alpha,red,green,blue),1,1) );
	buffer->Vertices.push_back(S3DVertex(+xwidth/2,+ywidth/2,+zwidth/2,0,0,+1,SColor(alpha,red,green,blue),1,0) );
	buffer->Vertices.push_back(S3DVertex(-xwidth/2,+ywidth/2,+zwidth/2,0,0,+1,SColor(alpha,red,green,blue),0,0) );

	buffer->Indices.push_back(4);
	buffer->Indices.push_back(5);
	buffer->Indices.push_back(6);

	buffer->Indices.push_back(4);
	buffer->Indices.push_back(6);
	buffer->Indices.push_back(7);

	// Left Face (8-11)
	buffer->Vertices.push_back(S3DVertex(-xwidth/2,-ywidth/2,-zwidth/2,0,-1,0,SColor(alpha,red,green,blue),0,0) );
	buffer->Vertices.push_back(S3DVertex(+xwidth/2,-ywidth/2,-zwidth/2,0,-1,0,SColor(alpha,red,green,blue),0,1) );
	buffer->Vertices.push_back(S3DVertex(+xwidth/2,-ywidth/2,+zwidth/2,0,-1,0,SColor(alpha,red,green,blue),1,1) );
	buffer->Vertices.push_back(S3DVertex(-xwidth/2,-ywidth/2,+zwidth/2,0,-1,0,SColor(alpha,red,green,blue),1,0) );

	buffer->Indices.push_back(8);
	buffer->Indices.push_back(9);
	buffer->Indices.push_back(10);

	buffer->Indices.push_back(8);
	buffer->Indices.push_back(10);
	buffer->Indices.push_back(11);

	// Right Face (12-15)
	buffer->Vertices.push_back(S3DVertex(-xwidth/2,+ywidth/2,-zwidth/2,0,+1,0,SColor(alpha,red,green,blue),0,0) );
	buffer->Vertices.push_back(S3DVertex(+xwidth/2,+ywidth/2,-zwidth/2,0,+1,0,SColor(alpha,red,green,blue),0,1) );
	buffer->Vertices.push_back(S3DVertex(+xwidth/2,+ywidth/2,+zwidth/2,0,+1,0,SColor(alpha,red,green,blue),1,1) );
	buffer->Vertices.push_back(S3DVertex(-xwidth/2,+ywidth/2,+zwidth/2,0,+1,0,SColor(alpha,red,green,blue),1,0) );

	buffer->Indices.push_back(12);
	buffer->Indices.push_back(14);
	buffer->Indices.push_back(13);

	buffer->Indices.push_back(12);
	buffer->Indices.push_back(15);
	buffer->Indices.push_back(14);

	// Front Face (16-19)
	buffer->Vertices.push_back(S3DVertex(-xwidth/2,-ywidth/2,-zwidth/2,0,-1,0,SColor(alpha,red,green,blue),0,0) );
	buffer->Vertices.push_back(S3DVertex(-xwidth/2,+ywidth/2,-zwidth/2,0,-1,0,SColor(alpha,red,green,blue),0,1) );
	buffer->Vertices.push_back(S3DVertex(-xwidth/2,+ywidth/2,+zwidth/2,0,-1,0,SColor(alpha,red,green,blue),1,1) );
	buffer->Vertices.push_back(S3DVertex(-xwidth/2,-ywidth/2,+zwidth/2,0,-1,0,SColor(alpha,red,green,blue),1,0) );

	buffer->Indices.push_back(16);
	buffer->Indices.push_back(18);
	buffer->Indices.push_back(17);

	buffer->Indices.push_back(16);
	buffer->Indices.push_back(19);
	buffer->Indices.push_back(18);

	// Back Face (20-23)
	buffer->Vertices.push_back(S3DVertex(+xwidth/2,-ywidth/2,-zwidth/2,0,+1,0,SColor(alpha,red,green,blue),0,0) );
	buffer->Vertices.push_back(S3DVertex(+xwidth/2,+ywidth/2,-zwidth/2,0,+1,0,SColor(alpha,red,green,blue),0,1) );
	buffer->Vertices.push_back(S3DVertex(+xwidth/2,+ywidth/2,+zwidth/2,0,+1,0,SColor(alpha,red,green,blue),1,1) );
	buffer->Vertices.push_back(S3DVertex(+xwidth/2,-ywidth/2,+zwidth/2,0,+1,0,SColor(alpha,red,green,blue),1,0) );

	buffer->Indices.push_back(20);
	buffer->Indices.push_back(21);
	buffer->Indices.push_back(22);

	buffer->Indices.push_back(20);
	buffer->Indices.push_back(22);
	buffer->Indices.push_back(23);

	mesh->addMeshBuffer(buffer);
	buffer->drop();

	// Add Bounding box
	mesh->setBoundingBox(aabbox3d<float>(-xwidth/2,-ywidth/2,-zwidth/2
			,+xwidth/2,+ywidth/2,+zwidth/2)) ;

	SAnimatedMesh *animmesh=new SAnimatedMesh();
	if(animmesh==NULL)
		{
		MStdPrintf("**Unable to load mesh %d\n",meshid);
		mesh->drop();
		return false;
		}

	animmesh->addMesh(mesh);
	mesh->drop();

	if(GMeshLoad(meshid,animmesh)==false)
		{
		MStdPrintf("**Unable to load mesh (%d)\n",meshid);
		animmesh->drop();
		return false;
		}
	
	return true;
	}


//////////////////////////////////////////////////
MDLLFUN bool CGLMeshLoadSphere(int meshid,float radius,int slices,int layers
			,int alpha,int red,int green,int blue)
	{
	if(GlobalInitialized==false)
		{
		MStdPrintf("**Engine is not initialized\n");
		return false;
		}

	if(slices<=1 || layers<=1)
		{
		return NULL;
		}

	SMesh *mesh=new SMesh;
	if(mesh==NULL)
		{
		return NULL;
		}

	// Allocate Buffer
	SMeshBuffer *buffer=new SMeshBuffer;
	if(buffer==NULL)
		{	
		mesh->drop();
		return NULL;		
		}

	float u=1.0f/3;
	float v=1.0f/2;
	float pi=(float)atan(1.0f)*4;
	float deltatheta=pi/layers;
	float deltaphi=2*pi/slices;
	float deltatexx=(float)(1.0f/slices);
	float deltatexy=(float)(1.0f/layers);
	int count=0;
	int i;
	for(i=0;i<layers;++i)
		{
		int k;
		for(k=0;k<slices;++k)
			{
			float theta=deltatheta*i;
			float phi=deltaphi*k;
			float x1=(float)(sin(theta)*cos(phi));
			float z1=(float)(sin(theta)*sin(phi));
			float y1=(float)(cos(theta));

			float x2=(float)(sin(theta+deltatheta)*cos(phi));
			float z2=(float)(sin(theta+deltatheta)*sin(phi));
			float y2=(float)(cos(theta+deltatheta));

			float x3=(float)(sin(theta+deltatheta)*cos(phi+deltaphi));
			float z3=(float)(sin(theta+deltatheta)*sin(phi+deltaphi));
			float y3=(float)(cos(theta+deltatheta));

			float x4=(float)(sin(theta)*cos(phi+deltaphi));
			float z4=(float)(sin(theta)*sin(phi+deltaphi));
			float y4=(float)(cos(theta));

			float texx=deltatexx*k;
			float texy=deltatexy*i;
	
			buffer->Vertices.push_back(S3DVertex(radius*x1,radius*y1,radius*z1
						,x1,y1,z1,SColor(alpha,red,green,blue),texx,texy) );
			buffer->Vertices.push_back(S3DVertex(radius*x2,radius*y2,radius*z2
						,x2,y2,z2,SColor(alpha,red,green,blue),texx,texy+deltatexy) );
			buffer->Vertices.push_back(S3DVertex(radius*x3,radius*y3,radius*z3
						,x3,y3,z3,SColor(alpha,red,green,blue),texx+deltatexx,texy+deltatexy) );
			buffer->Vertices.push_back(S3DVertex(radius*x4,radius*y4,radius*z4
						,x4,y4,z4,SColor(alpha,red,green,blue),texx+deltatexx,texy) );

			buffer->Indices.push_back(count);		
			buffer->Indices.push_back(count+2);		
			buffer->Indices.push_back(count+1);		
			buffer->Indices.push_back(count);		
			buffer->Indices.push_back(count+3);		
			buffer->Indices.push_back(count+2);	
			count=count+4;
			}
		}

	mesh->addMeshBuffer(buffer);
	
	// Add Bounding box
	mesh->setBoundingBox(aabbox3d<float>(-radius,-radius,-radius,radius,radius,radius)) ;

	SAnimatedMesh *animmesh=new SAnimatedMesh();
	if(animmesh==NULL)
		{
		mesh->drop();
		return false;
		}

	animmesh->addMesh(mesh);
	mesh->drop();

	if(GMeshLoad(meshid,animmesh)==false)
		{
		MStdPrintf("Unable to load mesh %d\n",meshid);
		animmesh->drop();
		return NULL;
		}

	return true;
	}


//////////////////////////////////////////////////
MDLLFUN bool CGLMeshExists(int meshid)
	{
	if(GlobalInitialized==false)
		{
		MStdPrintf("**Engine is not initialized\n");
		return false;
		}
	
	return GlobalMeshList.IsKey(meshid);
	}


//////////////////////////////////////////////////
MDLLFUN bool CGLMeshUnload(int meshid)
	{
	if(GlobalInitialized==false)
		{
		MStdPrintf("**Engine is not initialized\n");
		return false;
		}

	if(GlobalMeshList.IsKey(meshid)==false)
		{
		MStdPrintf("**Unable to unload mesh id %d\n",meshid);
		return false;
		}

	if(GlobalMeshList.Remove(meshid)==false)
		{
		MStdPrintf("**Unable to unload mesh %d\n",meshid);
		return false;
		}
	
	return true;
	}


/////////////////////////////////////////////////
MDLLFUN bool CGLMeshUnloadAll(void)
	{
	if(GlobalInitialized==false)
		{
		MStdPrintf("**Engine is not initialized\n");
		return false;
		}

	if(GlobalMeshList.Clear()==false)
		{
		MStdPrintf("**Unable to unload all meshs\n");
		return false;
		}

	return true;	
	}


////////////////////////////////////////////////
MDLLFUN int CGLMeshGetCount(void)
	{
	if(GlobalInitialized==false)
		{
		MStdPrintf("**Engine is not initialized\n");
		return 0;
		}

	return GlobalMeshList.GetCount();
	}


//////////////////////////////////////////////////
MDLLFUN bool CGLMeshAddVertex(int meshid,float x,float y,float z
				,float u,float v
				,int alpha,int red,int green,int blue
				,float n1,float n2,float n3)
	{
	if(GlobalInitialized==false)
		{
		MStdPrintf("**Engine is not initialized\n");
		return false;
		}
	
	MCGLMeshInfo *meshinfo=GlobalMeshList.Find(meshid);
	if(meshinfo==NULL)
		{
		MStdPrintf("Unable to find mesh %d\n",meshid);
		return false;
		}

	IAnimatedMesh *animmesh=meshinfo->GetMesh();
	IMesh *imesh=animmesh->getMesh(0);
	if(imesh==NULL)
		{
		MStdPrintf("Mesh %d not found\n",meshid);
		return false;
		}

	IMeshBuffer *meshbuffer=imesh->getMeshBuffer(0);
	if(meshbuffer==NULL)
		{
		return false;
		}

	if(meshbuffer->getVertexCount()>=65535)
		{
		MStdPrintf("**Maximum Vertex Count of 65535 has been reached\n");
		return false;
		}

	SMeshBuffer *smeshbuffer=(SMeshBuffer *)meshbuffer;

	smeshbuffer->Vertices.push_back(S3DVertex(x,y,z,n1,n2,n3
			,SColor(alpha,red,green,blue),u,v) );

	return true;
	}


//////////////////////////////////////////////////
MDLLFUN bool CGLMeshAddTriangle(int meshid,int index1,int index2,int index3)
	{
	if(GlobalInitialized==false)
		{
		MStdPrintf("**Engine is not initialized\n");
		return false;
		}
	
	MCGLMeshInfo *meshinfo=GlobalMeshList.Find(meshid);
	if(meshinfo==NULL)
		{
		MStdPrintf("Unable to find mesh %d\n",meshid);
		return false;
		}

	IAnimatedMesh *animmesh=meshinfo->GetMesh();
	if(animmesh==NULL)
		{
		return false;
		}

	if(animmesh->getFrameCount()<=0)
		{
		return false;
		}

	IMesh *mesh=animmesh->getMesh(0);
	if(mesh==NULL)
		{
		return false;
		}

	if(mesh->getMeshBufferCount()<=0)
		{
		return false;
		}

	IMeshBuffer *meshbuffer=mesh->getMeshBuffer(0);
	if(meshbuffer==NULL)
		{
		return false;
		}

	SMeshBuffer *smeshbuffer=(SMeshBuffer *)meshbuffer;

	int vertexcount=meshbuffer->getVertexCount();

	if(	index1<0 || index1>=vertexcount
			|| index2<0 || index2>=vertexcount
			|| index3<0 || index3>=vertexcount )
		{
		MStdPrintf("**Triangle Indices (%d,%d,%d) are bad.  Max vertices=%d\n"
				,index1,index2,index3,vertexcount);
		return false;
		}

	smeshbuffer->Indices.push_back(index1);
	smeshbuffer->Indices.push_back(index2);
	smeshbuffer->Indices.push_back(index3);
	
	return true;
	}


//////////////////////////////////////////////////////////////
MDLLFUN int CGLMeshGetVertexCount(int meshid)
	{
	if(GlobalInitialized==false)
		{
		MStdPrintf("**Engine is not initialized\n");
		return 0;
		}

	MCGLMeshInfo *meshinfo=GlobalMeshList.Find(meshid);
	if(meshinfo==NULL)
		{
		MStdPrintf("Unable to find mesh %d\n",meshid);
		return 0;
		}

	IAnimatedMesh *animmesh=meshinfo->GetMesh();
	if(animmesh==NULL)
		{
		return 0;
		}

	if(animmesh->getFrameCount()<=0)
		{
		return 0;
		}

	IMesh *mesh=meshinfo->GetMesh()->getMesh(0);
	if(mesh==NULL)
		{
		return 0;
		}

	if(mesh->getMeshBufferCount()<=0)
		{
		return 0;
		}

	IMeshBuffer *meshbuffer=mesh->getMeshBuffer(0);
	if(meshbuffer==NULL)
		{
		return 0;
		}

	return meshbuffer->getVertexCount();
	}


//////////////////////////////////////////////////////////////
MDLLFUN int CGLMeshGetTriangleCount(int meshid)
	{
	if(GlobalInitialized==false)
		{
		MStdPrintf("**Engine is not initialized\n");
		return 0;
		}

	MCGLMeshInfo *meshinfo=GlobalMeshList.Find(meshid);
	if(meshinfo==NULL)
		{
		MStdPrintf("Unable to find mesh %d\n",meshid);
		return 0;
		}

	IAnimatedMesh *animmesh=meshinfo->GetMesh();
	if(animmesh==NULL)
		{
		return 0;
		}

	if(animmesh->getFrameCount()<=0)
		{
		return 0;
		}

	IMesh *mesh=meshinfo->GetMesh()->getMesh(0);
	if(mesh==NULL)
		{
		return 0;
		}

	if(mesh->getMeshBufferCount()<=0)
		{
		return 0;
		}

	IMeshBuffer *meshbuffer=mesh->getMeshBuffer(0);
	if(meshbuffer==NULL)
		{
		return 0;
		}

	return meshbuffer->getIndexCount();
	}


